/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 */

package dao

import (
	"fmt"
	"prim-server/common"
	"prim-server/config"
	"prim-server/model"
	"prim-server/util"

	"github.com/pkg/errors"
)

const insertBatchSize = 20

var dataBase = config.GetConfig().DBConfig.DBName

type BillDao struct {
	BaseDao
}

type IBillDao interface {
	SaveBill(bill []model.Bill, billCycle string) error
	DeleteBill(tradeId string, billCycle string) error
	GetBills(queryParameter *common.QueryBillDetail) (bills []*model.Bill, totalCount int, err error)
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
func GetBillDao() IBillDao {
	dao := &BillDao{}
	dao.Init()
	return dao
}
func getTableNameWithBillCycle(billCycle string) string {
	tableName := "bill_" + billCycle
	return tableName
}
func (bd *BillDao) createBillTableWithBillCycle(billCycle string) error {
	tableName := getTableNameWithBillCycle(billCycle)
	sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s LIKE %s.bill", tableName, dataBase)
	_, err := bd.O.Raw(sql).Exec()
	if err != nil {
		return errors.Errorf("createBillTableWithBillCycle error: %v, billCycle: %s,dateBase: %s, tableName: %s", err, billCycle, dataBase, tableName)
	}
	return nil
}
func (bd *BillDao) buildSQLParameterForinsertBill() string {
	parameter := " (" + model.PlatformTypeFieldName + "," +
		model.BillIdFieldName + "," +
		model.UserIdFieldName + "," +
		model.CloudUserIdFieldName + "," +
		model.OrderIdFieldName + "," +
		model.BillCycleFieldName + "," +
		model.TradeIdFieldName + "," +
		model.TradeTimeFieldName + "," +
		model.EffectiveTimeFieldName + "," +
		model.ExpireTimeFieldName + "," +
		model.CurrencyFieldName + "," +
		model.ChargeModeFieldName + "," +
		model.BillTypeFieldName + "," +
		model.BillStatusFieldName + "," +
		model.CloudOrderIdFieldName + "," +
		model.CloudResourceIdFieldName + "," +
		model.ServiceTypeCodeFieldName + "," +
		model.ServiceTypeNameFieldName + "," +
		model.ResourceTypeCodeFieldName + "," +
		model.ResourceTypeNameFieldName + "," +
		model.RegionCodeFieldName + "," +
		model.RegionNameFieldName + "," +
		model.ConsumeAmountFieldName + "," +
		model.OfficialAmountFieldName + "," +
		model.OfficialDiscountAmountFieldName + "," +
		model.EraseAmountFieldName + "," +
		model.CashAmountFieldName + "," +
		model.CreditAmountFieldName + "," +
		model.CouponAmountFieldName + "," +
		model.FlexipurchaseCouponAmountFieldName + "," +
		model.StoredValueCardAmountFieldName + "," +
		model.DebtAmountFieldName + "," +
		model.WriteOffAmountFieldName + ") "
	return parameter
}
func (bd *BillDao) insertBill(bills []model.Bill, billCycle string) error {
	tableName := getTableNameWithBillCycle(billCycle)
	addNum := model.FieldNameNum - 1
	valuePart := "("
	for i := 0; i < addNum; i++ {
		valuePart += "?"
		if i != addNum-1 {
			valuePart += ","
		}
	}
	valuePart += ")"
	for i := 0; i < len(bills); i += insertBatchSize {
		batchBills := bills[i:min(i+insertBatchSize, len(bills))]
		sql := fmt.Sprintf("INSERT INTO %s.%s ", dataBase, tableName)
		parameter := bd.buildSQLParameterForinsertBill()
		values := " VALUES "
		insertParams := []interface{}{}
		for index, data := range batchBills {
			if index > 0 {
				values += ","
			}
			values += valuePart
			insertParams = append(insertParams,
				data.PlatformType, util.GetUUID32(), data.UserId, data.CloudUserId, data.OrderId, data.BillCycle, data.TradeId, data.TradeTime,
				data.EffectiveTime, data.ExpireTime, data.Currency, data.ChargeMode, data.BillType, data.BillStatus,
				data.CloudOrderId, data.CloudResourceId,
				data.ServiceTypeCode, data.ServiceTypeName, data.ResourceTypeCode,
				data.ResourceTypeName, data.RegionCode, data.RegionName,
				data.ConsumeAmount, data.OfficialAmount, data.OfficialDiscountAmount,
				data.EraseAmount, data.CashAmount, data.CreditAmount, data.CouponAmount,
				data.FlexipurchaseCouponAmount, data.StoredValueCardAmount, data.DebtAmount, data.WriteOffAmount)
		}
		//Raw可以对insertParams为空的情况进行处理
		sql = sql + parameter + values
		_, err := bd.O.Raw(sql, insertParams...).Exec()
		if err != nil {
			return err
		}
	}
	return nil
}
func (bd *BillDao) SaveBill(bill []model.Bill, billCycle string) error {
	if len(bill) == 0 || len(billCycle) == 0 {
		return nil
	}
	err := bd.createBillTableWithBillCycle(billCycle)
	if err != nil {
		return err
	}
	err = bd.insertBill(bill, billCycle)
	if err != nil {
		return errors.Errorf("SaveBill error: %v, billCycle: %s, bill array size: %v", err, billCycle, len(bill))
	}
	return nil
}

func (bd *BillDao) DeleteBill(billId string, billCycle string) error {
	if len(billId) == 0 || len(billCycle) == 0 {
		return nil
	}
	tableName := getTableNameWithBillCycle(billCycle)
	exist, err := bd.IsExistTable(tableName)
	if err != nil {
		return errors.Errorf("DeleteBill error: can't find the table, %v, billId: %s, billCycle: %s", err, billId, billCycle)
	}
	if !exist {
		return nil
	}
	sql := fmt.Sprintf("DELETE FROM %s.%s WHRER %s = %s ", dataBase, tableName, model.BillIdFieldName, billId)
	_, err = bd.O.Raw(sql).Exec()
	if err != nil {
		return errors.Errorf("DeleteBill error: %v, billId: %s, billCycle: %s", err, billId, billCycle)
	}
	return nil
}
func (bd *BillDao) buildSqlForGetBills(tableName string, queryParameter *common.QueryBillDetail) string {
	querySQL := fmt.Sprintf(" FROM %s.%s WHERE %s = '%s' ", dataBase, tableName, model.BillCycleFieldName, queryParameter.BillCycle)
	if len(queryParameter.PlatformType) > 0 {
		querySQL += fmt.Sprintf(" AND %s IN (%s)", model.PlatformTypeFieldName, bd.JoinArray(queryParameter.PlatformType))
	}
	billIdSQL := ""
	if len(queryParameter.BillId) > 0 {
		billIdSQL = fmt.Sprintf(" %s IN (%s)", model.BillIdFieldName, bd.JoinArray(queryParameter.BillId))
	}
	orderIdSQL := ""
	if len(queryParameter.OrderId) > 0 {
		orderIdSQL = fmt.Sprintf(" %s IN (%s)", model.OrderIdFieldName, bd.JoinArray(queryParameter.OrderId))
	}
	if len(billIdSQL) > 0 && len(orderIdSQL) > 0 {
		querySQL += fmt.Sprintf(" AND ( %s OR %s ) ", billIdSQL, orderIdSQL)
	} else {
		if len(billIdSQL) > 0 {
			querySQL += fmt.Sprintf(" AND %s ", billIdSQL)
		} else if len(orderIdSQL) > 0 {
			querySQL += fmt.Sprintf(" AND %s ", orderIdSQL)
		} else {
			// no filter
		}
	}
	if len(queryParameter.UserId) > 0 {
		querySQL += fmt.Sprintf(" AND %s IN (%s)", model.UserIdFieldName, bd.JoinArray(queryParameter.UserId))
	}
	if len(queryParameter.ChargeMode) > 0 {
		querySQL += fmt.Sprintf(" AND %s IN (%s)", model.ChargeModeFieldName, bd.JoinArray(queryParameter.ChargeMode))
	}
	if len(queryParameter.BillType) > 0 {
		querySQL += fmt.Sprintf(" AND %s IN (%s)", model.BillTypeFieldName, bd.JoinArray(queryParameter.BillType))
	}
	if len(queryParameter.BillStatus) > 0 {
		querySQL += fmt.Sprintf(" AND %s IN (%s)", model.BillStatusFieldName, bd.JoinArray(queryParameter.BillStatus))
	}
	if queryParameter.TradeTimeBegin != nil && queryParameter.TradeTimeEnd != nil {
		querySQL += fmt.Sprintf(" AND ( %s >= '%s' AND %s <= '%s' )", model.TradeTimeFieldName, bd.ConvertToUTC(*queryParameter.TradeTimeBegin), model.TradeTimeFieldName, bd.ConvertToUTC(*queryParameter.TradeTimeEnd))
	}
	if queryParameter.TimeReverseOrder == nil {
		querySQL += fmt.Sprintf(" ORDER BY %s DESC", model.TradeTimeFieldName)
	} else {
		if !(*queryParameter.TimeReverseOrder) {
			querySQL += fmt.Sprintf(" ORDER BY %s ASC", model.TradeTimeFieldName)
		}
	}
	return querySQL
}
func (bd *BillDao) GetBills(queryParameter *common.QueryBillDetail) (bills []*model.Bill, totalCount int, err error) {
	if queryParameter == nil {
		return nil, 0, nil
	}
	if len(queryParameter.BillCycle) == 0 {
		return nil, 0, nil
	}
	tableName := getTableNameWithBillCycle(queryParameter.BillCycle)
	exist, err := bd.IsExistTable(tableName)
	if err != nil {
		return nil, 0, errors.Errorf("GetBills error: can't find the table: %v, userId: %s, billCycle: %s", err, fmt.Sprint(queryParameter.UserId), queryParameter.BillCycle)
	}
	if !exist {
		return nil, 0, nil
	}
	querySQL := bd.buildSqlForGetBills(tableName, queryParameter)
	countSQL := fmt.Sprintf("SELECT COUNT(*) %s", querySQL)
	offset, pageSize := bd.GetOffsetAndPageSize(queryParameter.Page, queryParameter.PageSize)
	dataSQL := fmt.Sprintf("SELECT * %s LIMIT %d OFFSET %d", querySQL, pageSize, offset)
	err = bd.O.Raw(countSQL).QueryRow(&totalCount)
	if err != nil {
		return nil, 0, err
	}
	_, err = bd.O.Raw(dataSQL).QueryRows(&bills)
	if err != nil {
		return nil, 0, errors.Errorf("GetBills error: %v, userId: %s, billCycle: %s", err, fmt.Sprint(queryParameter.UserId), queryParameter.BillCycle)
	}
	return bills, totalCount, nil
}
